As we showed in Layout and Styling of Jupyter widgets multiple widgets can be arranged together using the flexible GridBox specification. However, use of the specification involves some understanding of CSS properties and may impose sharp learning curve. Here, we will describe layout templates built on top of GridBox
that simplify creation of common widget layouts.
In [ ]:
# Utils widgets
from ipywidgets import Button, Layout, jslink, IntText, IntSlider
def create_expanded_button(description, button_style):
return Button(description=description, button_style=button_style, layout=Layout(height='auto', width='auto'))
top_left_button = create_expanded_button("Top left", 'info')
top_right_button = create_expanded_button("Top right", 'success')
bottom_left_button = create_expanded_button("Bottom left", 'danger')
bottom_right_button = create_expanded_button("Bottom right", 'warning')
top_left_text = IntText(description='Top left', layout=Layout(width='auto', height='auto'))
top_right_text = IntText(description='Top right', layout=Layout(width='auto', height='auto'))
bottom_left_slider = IntSlider(description='Bottom left', layout=Layout(width='auto', height='auto'))
bottom_right_slider = IntSlider(description='Bottom right', layout=Layout(width='auto', height='auto'))
You can easily create a layout with 4 widgets arranged on 2x2 matrix using the TwoByTwoLayout
widget:
In [ ]:
from ipywidgets import TwoByTwoLayout
TwoByTwoLayout(top_left=top_left_button,
top_right=top_right_button,
bottom_left=bottom_left_button,
bottom_right=bottom_right_button)
If you don't define a widget for some of the slots, the layout will automatically re-configure itself by merging neighbouring cells
In [ ]:
TwoByTwoLayout(top_left=top_left_button,
bottom_left=bottom_left_button,
bottom_right=bottom_right_button)
You can pass merge=False
in the argument of the TwoByTwoLayout
constructor if you don't want this behavior
In [ ]:
TwoByTwoLayout(top_left=top_left_button,
bottom_left=bottom_left_button,
bottom_right=bottom_right_button,
merge=False)
You can add a missing widget even after the layout initialization:
In [ ]:
layout_2x2 = TwoByTwoLayout(top_left=top_left_button,
bottom_left=bottom_left_button,
bottom_right=bottom_right_button)
layout_2x2
In [ ]:
layout_2x2.top_right = top_right_button
You can also use the linking feature of widgets to update some property of a widget based on another widget:
In [ ]:
app = TwoByTwoLayout(top_left=top_left_text, top_right=top_right_text,
bottom_left=bottom_left_slider, bottom_right=bottom_right_slider)
link_left = jslink((app.top_left, 'value'), (app.bottom_left, 'value'))
link_right = jslink((app.top_right, 'value'), (app.bottom_right, 'value'))
app.bottom_right.value = 30
app.top_left.value = 25
app
You can easily create more complex layouts with custom widgets. For example, you can use bqplot Figure widget to add plots:
In [ ]:
import bqplot as bq
import numpy as np
In [ ]:
size = 100
np.random.seed(0)
x_data = range(size)
y_data = np.random.randn(size)
y_data_2 = np.random.randn(size)
y_data_3 = np.cumsum(np.random.randn(size) * 100.)
x_ord = bq.OrdinalScale()
y_sc = bq.LinearScale()
bar = bq.Bars(x=np.arange(10), y=np.random.rand(10), scales={'x': x_ord, 'y': y_sc})
ax_x = bq.Axis(scale=x_ord)
ax_y = bq.Axis(scale=y_sc, tick_format='0.2f', orientation='vertical')
fig = bq.Figure(marks=[bar], axes=[ax_x, ax_y], padding_x=0.025, padding_y=0.025,
layout=Layout(width='auto', height='90%'))
In [ ]:
from ipywidgets import FloatSlider
max_slider = FloatSlider(min=0, max=10, default_value=2, description="Max: ",
layout=Layout(width='auto', height='auto'))
min_slider = FloatSlider(min=-1, max=10, description="Min: ",
layout=Layout(width='auto', height='auto'))
app = TwoByTwoLayout(top_left=min_slider,
bottom_left=max_slider,
bottom_right=fig,
align_items="center",
height='700px')
jslink((y_sc, 'max'), (max_slider, 'value'))
jslink((y_sc, 'min'), (min_slider, 'value'))
jslink((min_slider, 'max'), (max_slider, 'value'))
jslink((max_slider, 'min'), (min_slider, 'value'))
max_slider.value = 1.5
app
AppLayout
is a widget layout template that allows you to create an application-like widget arrangements. It consist of a header, a footer, two sidebars and a central pane:
In [ ]:
from ipywidgets import AppLayout, Button, Layout
In [ ]:
header_button = create_expanded_button('Header', 'success')
left_button = create_expanded_button('Left', 'info')
center_button = create_expanded_button('Center', 'warning')
right_button = create_expanded_button('Right', 'info')
footer_button = create_expanded_button('Footer', 'success')
In [ ]:
AppLayout(header=header_button,
left_sidebar=left_button,
center=center_button,
right_sidebar=right_button,
footer=footer_button)
However with the automatic merging feature, it's possible to achieve many other layouts:
In [ ]:
AppLayout(header=None,
left_sidebar=None,
center=center_button,
right_sidebar=None,
footer=None)
In [ ]:
AppLayout(header=header_button,
left_sidebar=left_button,
center=center_button,
right_sidebar=right_button,
footer=None)
In [ ]:
AppLayout(header=None,
left_sidebar=left_button,
center=center_button,
right_sidebar=right_button,
footer=None)
In [ ]:
AppLayout(header=header_button,
left_sidebar=left_button,
center=center_button,
right_sidebar=None,
footer=footer_button)
In [ ]:
AppLayout(header=header_button,
left_sidebar=None,
center=center_button,
right_sidebar=right_button,
footer=footer_button)
In [ ]:
AppLayout(header=header_button,
left_sidebar=None,
center=center_button,
right_sidebar=None,
footer=footer_button)
In [ ]:
AppLayout(header=header_button,
left_sidebar=left_button,
center=None,
right_sidebar=right_button,
footer=footer_button)
You can also modify the relative and absolute widths and heights of the panes using pane_widths
and pane_heights
arguments. Both accept a sequence of three elements, each of which is either an integer (equivalent to the weight given to the row/column) or a string in the format '1fr'
(same as integer) or '100px'
(absolute size).
In [ ]:
AppLayout(header=header_button,
left_sidebar=left_button,
center=center_button,
right_sidebar=right_button,
footer=footer_button,
pane_widths=[3, 3, 1],
pane_heights=[1, 5, '60px'])
GridspecLayout
is a N-by-M grid layout allowing for flexible layout definitions using an API similar to matplotlib's GridSpec.
You can use GridspecLayout
to define a simple regularly-spaced grid. For example, to create a 4x3 layout:
In [ ]:
from ipywidgets import GridspecLayout
grid = GridspecLayout(4, 3)
for i in range(4):
for j in range(3):
grid[i, j] = create_expanded_button('Button {} - {}'.format(i, j), 'warning')
grid
To make a widget span several columns and/or rows, you can use slice notation:
In [ ]:
grid = GridspecLayout(4, 3, height='300px')
grid[:3, 1:] = create_expanded_button('One', 'success')
grid[:, 0] = create_expanded_button('Two', 'info')
grid[3, 1] = create_expanded_button('Three', 'warning')
grid[3, 2] = create_expanded_button('Four', 'danger')
grid
You can still change properties of the widgets stored in the grid, using the same indexing notation.
In [ ]:
grid = GridspecLayout(4, 3, height='300px')
grid[:3, 1:] = create_expanded_button('One', 'success')
grid[:, 0] = create_expanded_button('Two', 'info')
grid[3, 1] = create_expanded_button('Three', 'warning')
grid[3, 2] = create_expanded_button('Four', 'danger')
grid
In [ ]:
grid[0, 0].description = "I am the blue one"
Note: It's enough to pass an index of one of the grid cells occupied by the widget of interest. Slices are not supported in this context.
If there is already a widget that conflicts with the position of the widget being added, it will be removed from the grid:
In [ ]:
grid = GridspecLayout(4, 3, height='300px')
grid[:3, 1:] = create_expanded_button('One', 'info')
grid[:, 0] = create_expanded_button('Two', 'info')
grid[3, 1] = create_expanded_button('Three', 'info')
grid[3, 2] = create_expanded_button('Four', 'info')
grid
In [ ]:
grid[3, 1] = create_expanded_button('New button!!', 'danger')
Note: Slices are supported in this context.
In [ ]:
grid[:3, 1:] = create_expanded_button('I am new too!!!!!', 'warning')
In these examples, we will demonstrate how to use GridspecLayout
and bqplot
widget to create a multipanel scatter plot. To run this example you will need to install the bqplot package.
For example, you can use the following snippet to obtain a scatter plot across multiple dimensions:
In [ ]:
import bqplot as bq
import numpy as np
from ipywidgets import GridspecLayout, Button, Layout
n_features = 5
data = np.random.randn(100, n_features)
data[:50, 2] += 4 * data[:50, 0] **2
data[50:, :] += 4
A = np.random.randn(n_features, n_features)/5
data = np.dot(data,A)
scales_x = [bq.LinearScale() for i in range(n_features)]
scales_y = [bq.LinearScale() for i in range(n_features)]
gs = GridspecLayout(n_features, n_features)
for i in range(n_features):
for j in range(n_features):
if i != j:
sc_x = scales_x[j]
sc_y = scales_y[i]
scatt = bq.Scatter(x=data[:, j], y=data[:, i], scales={'x': sc_x, 'y': sc_y}, default_size=1)
gs[i, j] = bq.Figure(marks=[scatt], layout=Layout(width='auto', height='auto'),
fig_margin=dict(top=0, bottom=0, left=0, right=0))
else:
sc_x = scales_x[j]
sc_y = bq.LinearScale()
hist = bq.Hist(sample=data[:,i], scales={'sample': sc_x, 'count': sc_y})
gs[i, j] = bq.Figure(marks=[hist], layout=Layout(width='auto', height='auto'),
fig_margin=dict(top=0, bottom=0, left=0, right=0))
gs
You can specify extra style properties to modify the layout. For example, you can change the size of the whole layout using the height
and width
arguments.
In [ ]:
AppLayout(header=None,
left_sidebar=left_button,
center=center_button,
right_sidebar=right_button,
footer=None,
height="200px", width="50%")
The gap between the panes can be increase or decreased with grid_gap
argument:
In [ ]:
AppLayout(header=None,
left_sidebar=left_button,
center=center_button,
right_sidebar=right_button,
footer=None,
height="200px", width="50%",
grid_gap="10px")
Additionally, you can control the alignment of widgets within the layout using justify_content
and align_items
attributes:
In [ ]:
from ipywidgets import Text, HTML
TwoByTwoLayout(top_left=top_left_button, top_right=top_right_button,
bottom_right=bottom_right_button,
justify_items='center',
width="50%",
align_items='center')
For other alignment options it's possible to use common names (top
and bottom
) or their CSS equivalents (flex-start
and flex-end
):
In [ ]:
TwoByTwoLayout(top_left=top_left_button, top_right=top_right_button,
bottom_right=bottom_right_button,
justify_items='center',
width="50%",
align_items='top')